#ifndef PYTABLESTABLEWRITER_H_
#define PYTABLESTABLEWRITER_H_

#include <cmath>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>

#include <hdf5.h>

#include "table_exception.hpp"
#include "table.hpp"

namespace pyt {
  template <class T> class table_writer: public table<T> {
  public:
    table_writer(const std::string& fname, const std::string& tblname, 
		 const std::string& title, hsize_t expected_rows, unsigned int comp_level); 
    virtual ~table_writer();
    hsize_t nrows() {
      if (this->_irow != 0)
	flush();
      return this->_nrows;
    }

    herr_t read_row(T& row_out);
    herr_t append(T& row_out);

    herr_t table_setattr(const std::string& name, const int val);
    herr_t table_setattr(const std::string& name, const float val);
    herr_t table_setattr(const std::string& name, const double val);
    herr_t table_setattr(const std::string& name, const std::string& val);
    
    herr_t file_setattr(const std::string& name, const std::string& val);
    
    void reset();

  protected:
    herr_t flush();
    herr_t set_str_attr(hid_t id, std::string name, std::string val);
    herr_t set_int_attr(hid_t id, std::string name, int val);
    herr_t set_hsize_attr(hid_t id, std::string name, hsize_t val);
    herr_t set_float_attr(hid_t id, std::string name, float val);
    herr_t set_double_attr(hid_t id, std::string name, double val);

    hsize_t calc_chunksize(hsize_t expected_nrows);
    hsize_t calc_bufsize(hsize_t expected_nrows);

  private:
    hsize_t _maxdim;
    int _fillvalue; 
    std::string _title;
  };

  template<class T> table_writer<T>::table_writer(const std::string& fname, 
						  const std::string& tblname, 
						  const std::string& title, 
						  hsize_t expected_nrows, 
						  unsigned int comp_level): 
    table<T>(fname, tblname),
    _title(title)
  {
    this->_chunksize = calc_chunksize(expected_nrows);
    this->_bufsize   = (hsize_t) (this->_chunksize * parameters::BUF_TO_CHUNK_RATIO);

    this->_rank      = 1; 
    this->_maxdim    = H5S_UNLIMITED;
    this->_idfile    = H5Fcreate(this->_fname.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
   
    hsize_t min_size = 0;
    this->_idfsp     = H5Screate_simple(this->_rank, &min_size, &_maxdim);
    this->_cparms    = H5Pcreate(H5P_DATASET_CREATE);
    this->_buf       = new T[this->_bufsize];
    this->_nrows     = 0;
    
    this->_status   |= H5Pset_chunk(this->_cparms, this->_rank, &this->_chunksize);
    this->_status   |= H5Pset_fill_value(this->_cparms, T::h5_type(), &_fillvalue );

    if (comp_level > 0) {
      this->_status   |= H5Pset_shuffle(this->_cparms);
      this->_status   |= H5Pset_deflate(this->_cparms, comp_level);
    }

    this->_idtbl     = H5Dcreate2(this->_idfile, this->_tblname.c_str(), T::h5_type(), this->_idfsp, H5P_DEFAULT,
				  this->_cparms, H5P_DEFAULT);

    if (this->_status != 0)
      throw table_exception("Initialization of table failed: overall status non-zero");
  }
  
  template <class T> table_writer<T>::~table_writer() {  
    if (this->_irow > 0)
      flush();

    set_str_attr(this->_idfile, "CLASS", "GROUP");
    set_str_attr(this->_idfile, "PYTABLES_FORMAT_VERSION", "2.0");
    set_str_attr(this->_idfile, "TITLE", _title.c_str());
    set_str_attr(this->_idfile, "VERSION", "1.0");
  
    set_str_attr(this->_idtbl, "CLASS", "TABLE");
    set_str_attr(this->_idtbl, "TITLE", _title.c_str());
    set_str_attr(this->_idtbl, "VERSION", "2.6");

    std::vector<std::string> field_names = T::field_names();
    std::vector<hid_t> field_types = T::field_types();

    std::stringstream ss;
    for (size_t i=0; i < field_names.size(); i++) {
      ss << "FIELD_" << i << "_NAME";
      set_str_attr(this->_idtbl, ss.str().c_str(), field_names[i]);
      ss.str("");
      ss << "FIELD_" << i << "_FILL";
      if (field_types[i] == H5T_NATIVE_INT)
	set_int_attr(this->_idtbl, ss.str().c_str(), 0);
      else if (field_types[i] == H5T_NATIVE_FLOAT)
	set_float_attr(this->_idtbl, ss.str().c_str(), 0);
      else 
	set_double_attr(this->_idtbl, ss.str().c_str(), 0);

      ss.str("");
    }

    set_hsize_attr(this->_idtbl, "NROWS", this->_nrows);
    
    H5Sclose(this->_idfsp);
    H5Pclose(this->_cparms);
    H5Dclose(this->_idtbl);
    H5Fclose(this->_idfile);

    delete[] this->_buf;
  }



  template <class T> herr_t table_writer<T>::read_row(T &row) {
    throw table_exception("Cannot read from table opened in write-only mode.");
  }
  
  template <class T> herr_t table_writer<T>::append(T &row) {
    if (this->_irow >= this->_bufsize) {
      this->_status |= flush();    
    }
    
    this->_buf[this->_irow++] = row;
    return this->_status;
  }
    
  template <class T> herr_t table_writer<T>::flush() {
    this->_nrows  += this->_irow; 
    this->_status |= H5Dset_extent(this->_idtbl, &this->_nrows);
    this->_idfsp   = H5Dget_space(this->_idtbl);
    this->_status |= H5Sselect_hyperslab(this->_idfsp, H5S_SELECT_SET, &this->_offset, NULL, &this->_irow, NULL);
    hid_t dsp      = H5Screate_simple(this->_rank, &this->_irow, NULL);
    
    this->_status |= H5Dwrite(this->_idtbl, T::h5_type(), dsp, this->_idfsp,
			      H5P_DEFAULT, this->_buf);

    H5Sclose(dsp);
    
    this->_offset += this->_irow;
    this->_irow    = 0; 
  
    return this->_status;
  }

  template <class T> void table_writer<T>::reset() {
     throw table_exception("Cannot reset a table opened in write-only mode.");
  }
  
  template <class T> herr_t 
  table_writer<T>::table_setattr(const std::string& name, 
				 const std::string& value) {
    return this->set_str_attr(this->_idtbl, name, value);
  }

  template <class T> herr_t 
  table_writer<T>::table_setattr(const std::string& name, const int value) {
    return this->set_int_attr(this->_idtbl, name, value);
  }

  template <class T> herr_t 
  table_writer<T>::table_setattr(const std::string& name, const float value) {
    return this->set_float_attr(this->_idtbl, name, value);
  }

  template <class T> herr_t 
  table_writer<T>::table_setattr(const std::string& name, const double value) {
    return this->set_double_attr(this->_idtbl, name, value);
  }


  template <class T> herr_t table_writer<T>::file_setattr(const std::string& name, const std::string& value) {
    return this->set_str_attr(this->_idfile, name, value);
  }

  template <class T> herr_t table_writer<T>::set_str_attr(hid_t id, std::string name, std::string val) {
    herr_t status = 0;
  
    hid_t dsp  = H5Screate(H5S_SCALAR);
    hid_t type = H5Tcopy(H5T_C_S1);
    H5Tset_size(type, val.size());
    H5Tset_strpad(type, H5T_STR_NULLTERM);
    hid_t attr = H5Acreate2(id, name.c_str(), type, dsp, H5P_DEFAULT, H5P_DEFAULT);
    status = H5Awrite(attr, type, val.c_str());
  
    status |= H5Sclose(dsp);
    status |= H5Tclose(type);
    status |= H5Aclose(attr);
    
    return status;
  }

  template <class T> herr_t table_writer<T>::set_hsize_attr(hid_t id, std::string name, hsize_t val) {
    herr_t status = 0;
    hid_t dsp  = H5Screate(H5S_SCALAR);
    hid_t attr = H5Acreate2(id, name.c_str(), H5T_NATIVE_HSIZE, dsp, H5P_DEFAULT, H5P_DEFAULT);
    status = H5Awrite(attr, H5T_NATIVE_HSIZE, &val);
    
    status |= H5Sclose(dsp);
    status |= H5Aclose(attr);
    
    return status;
  }

  template <class T> herr_t table_writer<T>::set_int_attr(hid_t id, std::string name, int val) {
    herr_t status = 0;
    hid_t dsp  = H5Screate(H5S_SCALAR);
    hid_t attr = H5Acreate2(id, name.c_str(), H5T_NATIVE_INT, dsp, H5P_DEFAULT, H5P_DEFAULT);
    status = H5Awrite(attr, H5T_NATIVE_INT, &val);
    
    status |= H5Sclose(dsp);
    status |= H5Aclose(attr);
    
    return status;
  }

  template <class T> herr_t table_writer<T>::set_float_attr(hid_t id, std::string name, float val) {
    herr_t status = 0;
    hid_t dsp  = H5Screate(H5S_SCALAR);
    hid_t attr = H5Acreate2(id, name.c_str(), H5T_NATIVE_FLOAT, dsp, H5P_DEFAULT, H5P_DEFAULT);
    status = H5Awrite(attr, H5T_NATIVE_FLOAT, &val);
    
    status |= H5Sclose(dsp);
    status |= H5Aclose(attr);
    
    return status;
  }

  template <class T> herr_t table_writer<T>::set_double_attr(hid_t id, std::string name, double val) {
    herr_t status = 0;
    hid_t dsp  = H5Screate(H5S_SCALAR);
    hid_t attr = H5Acreate2(id, name.c_str(), H5T_NATIVE_DOUBLE, dsp, H5P_DEFAULT, H5P_DEFAULT);
    status = H5Awrite(attr, H5T_NATIVE_DOUBLE, &val);
    
    status |= H5Sclose(dsp);
    status |= H5Aclose(attr);
    
    return status;
  }

  template <class T> hsize_t table_writer<T>::calc_chunksize(hsize_t expected_rows) {
    hsize_t MB = 1024 * 1024;

    if (expected_rows == 0)
      expected_rows = parameters::DEFAULT_NROWS;

    hsize_t expected_size_in_MB = (hsize_t) sizeof(T) * expected_rows / MB; 

    if (expected_size_in_MB < 1)
      expected_size_in_MB = 1;
    else if (expected_size_in_MB > 1e7)
      expected_size_in_MB = (hsize_t)1e7;

    
    hsize_t basesize              = 8 * 1024;
    hsize_t zone                  = (hsize_t) log10(expected_size_in_MB);
    expected_size_in_MB           = (hsize_t) pow(10.0, (double)zone);
    hsize_t chunkbytes            = (hsize_t) (basesize * pow(2.0, (double)zone));
    
    hsize_t chunksize = (hsize_t) floor(chunkbytes / sizeof(T));
    
    if (chunksize < 1)
      chunksize = 1;

    return chunksize;
  }
}



#endif //PYTABLESTABLEWRITER_H_

